package com.pocket.insight.base.sql.dataset;

import com.pocket.insight.base.mql.EvalAdapter;
import com.pocket.insight.entity.Dataset;
import com.pocket.insight.entity.DatasetField;
import com.pocket.insight.entity.ME;
import com.pocket.insight.entity.Type;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 描述：
 *
 * @author shuize
 * @create 2024/12/5
 */
@Slf4j
public class DatasetUtil {

    public static Long    DATASET_ID_REVISE           = Long.valueOf(1000000L);
    public static Integer EXTEND_DATASET_FIELD_REVISE = Integer.valueOf(1000000);
    public static Integer REF_DATASET_FIELD_REVISE    = Integer.valueOf(100000);

    public static List<DatasetSource> fetchDatasetsInFilter(Dataset.Options datasetOptions, Long appId, Long datasetId) {
        List<DatasetSource> upstream = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(datasetOptions.getWhere())) {
            EvalAdapter.getDatasetInTableFunction(ME.from(datasetOptions.getWhere()))
                    .forEach(d -> upstream.add(new DatasetSource(datasetId, d, DatasetSource.DatasetSourceType.inner_filter.toString(), appId, appId)));
        }
        if (datasetOptions.fetchType() == Dataset.Options.DatasetTypeEnum.union) {
            datasetOptions.getUnionOptions().getFieldsMapping()
                    .forEach(fieldMapping -> {
                        EvalAdapter.getDatasetInTableFunction(ME.from(datasetOptions.getWhere()))
                                .forEach(d -> upstream.add(new DatasetSource(datasetId, d, DatasetSource.DatasetSourceType.inner_filter.toString(), appId, appId)));
            });
        }

        return upstream;
    }

    public static Pair<Long, Long> getAppDatasetIdFromId(Long appId, Long datasetId) {
        if (datasetId != null && datasetId.longValue() / DATASET_ID_REVISE.longValue() != 0L) {
            Long newAppId = Long.valueOf(datasetId.longValue() / DATASET_ID_REVISE.longValue());
            Long newDatasetId = Long.valueOf(datasetId.longValue() % DATASET_ID_REVISE.longValue());
            return (Pair<Long, Long>) new ImmutablePair(newAppId, newDatasetId);
        }
        return (Pair<Long, Long>) new ImmutablePair(appId, datasetId);
    }

    public static List<Dataset.Field> mergeSchemaKeepFieldsOrder(Map<String, Dataset.Field> userSchemaMap, List<Dataset.Field> fields, boolean caseSensitive) {
        return mergeSchemaKeepFieldsOrder(userSchemaMap, fields, caseSensitive, true);
    }


    public static List<Dataset.Field> mergeSchemaKeepFieldsOrder(Map<String, Dataset.Field> userSchemaMap, List<Dataset.Field> fields, boolean caseSensitive, boolean addNotFoundUserAddedField) {
        if (userSchemaMap.isEmpty()) {
            return fields;
        } else if (fields == null) {
            return new ArrayList(userSchemaMap.values());
        } else {
            Function<Dataset.Field, String> keyGetter = caseSensitive ? Dataset.Field::getFieldName : (field) -> {
                return StringUtils.lowerCase(field.getFieldName());
            };
            Map<String, Dataset.Field> userSchemaMapFinal = caseSensitive ? userSchemaMap : toMapWithKeyGetter(userSchemaMap, keyGetter);
            Set<String> fieldNames = new HashSet();
            fields.forEach((field) -> {
                Dataset.Field userField = userSchemaMapFinal.get(keyGetter.apply(field));
                if (userField != null) {
                    mergeField(field, userField);
                    if (!caseSensitive && userField != null) {
                        field.setFieldName(userField.getFieldName());
                    }
                    fieldNames.add(keyGetter.apply(field));
                }
            });
            if (addNotFoundUserAddedField) {
                fields.addAll(userSchemaMap.values().stream().filter((field) -> {
                    return !fieldNames.contains(keyGetter.apply(field)) && field.isUserAdded();
                }).collect(Collectors.toList()));
            }

            return fields;
        }
    }

    private static Map<String, Dataset.Field> toMapWithKeyGetter(Map<String, Dataset.Field> fromMap, Function<Dataset.Field, String> keyGetter) {
        Map<String, Dataset.Field> toRet = new LinkedHashMap();
        MapUtils.emptyIfNull(fromMap).forEach((k, v) -> {
            String key = keyGetter.apply(v);
            if (StringUtils.isNotBlank(key)) {
                toRet.put(key, v);
            }
        });
        return toRet;
    }


    public static void mergeField(Dataset.Field toField, Dataset.Field fromFieldOri) {
        if (fromFieldOri != null) {
            Dataset.Field fromField = fromFieldOri.partialDeepCopy();
            if (fromField != null && toField != null) {
                List<Type.FieldType> suggestedTypes = toField.getGeneratedOptions().getSuggestedTypes();
                if (fromField.getOptions().getType() != null && !suggestedTypes.isEmpty() && suggestedTypes.stream().anyMatch((suggestedType) -> {
                    return suggestedType.type != Type.TypeName.unknown;
                }) && suggestedTypes.stream().noneMatch((fieldType) -> {
                    return fieldType.type.type().getDisplayTypeName() == fromField.getType();
                }) && (fromField.getType() != Type.TypeName.date || !fromField.hasUserDateFormat())) {
                    fromField.getOptions().setType( null);
                }

                if (StringUtils.isNotBlank(toField.getConfig().dateFormat) && !Objects.equals(toField.getOptions().getConfig().dateFormat, fromField.getOptions().getConfig().dateFormat)) {
                    fromField.getOptions().getConfig().dateFormat = null;
                }
                toField.setOptions(fromField.getOptions());
                DatasetField.GeneratedOptions toGeneratedOptions = toField.getGeneratedOptions();
                DatasetField.GeneratedOptions fromGeneratedOptions = fromField.getGeneratedOptions();
            }
        }
    }

    public static Map<String, Dataset.Field> toSchemaMap(List<Dataset.Field> schema) {
        return CollectionUtils.emptyIfNull(schema).stream().collect(
                Collectors.toMap(Dataset.Field::getFieldName, f -> f, (e1, e2) -> e1, LinkedHashMap::new)
        );
    }


    public static List<DatasetSource> fetchUpstreamDatasets(Dataset.Options datasetOptions, Long appId, Long datasetId) {
        List<DatasetSource> upstream = new ArrayList();
        if (datasetOptions.isReference()) {
            upstream.add(new DatasetSource(datasetId, datasetOptions.getReferenceOptions().getSourceDatasetId(), datasetOptions.fetchType().toString(), appId, datasetOptions.getReferenceOptions().getSourceAppId()));
        } else if (datasetOptions.isExtendDataset()) {
            upstream.add(new DatasetSource(datasetId, datasetOptions.getExtendOptions().getBaseId(), datasetOptions.fetchType().toString(), appId, appId));
        } else if (datasetOptions.isUnion()) {
            Dataset.Options.UnionOptions unionOpts = datasetOptions.getUnionOptions();
            if (unionOpts != null) {
                unionOpts.fetchFieldsMapping().forEach(
                        o -> upstream.add(new DatasetSource(datasetId, o.getDatasetId(), datasetOptions.fetchType().toString(), appId, appId))
                );
            }
        } else if (datasetOptions.isFusion()) {
            upstream.add(new DatasetSource(datasetId, datasetOptions.getRootDatasetId(), datasetOptions.fetchType().toString(), appId, appId));
            List<Dataset.Options.JoinOpt> joinOpts = datasetOptions.getJoinOpts();
            if (joinOpts != null) {
                joinOpts.forEach((joinOpt) -> {
                    if (joinOpt.getJoinDatasetId() != null) {
                        if (upstream.stream().noneMatch((e) -> {
                            return e != null && Objects.equals(e.getSourceId(), joinOpt.getJoinDatasetId()) && Objects.equals(e.getSourceAppId(), appId);
                        })) {
                            upstream.add(new DatasetSource(datasetId, joinOpt.getJoinDatasetId(), datasetOptions.fetchType().toString(), appId, appId));
                        }

                    }
                });
            }
        } else if (datasetOptions.isAggregate()) {
            upstream.add(new DatasetSource(datasetId, datasetOptions.getRootDatasetId(), datasetOptions.fetchType().toString(), appId, appId));
        } else if (datasetOptions.isPivot() || datasetOptions.isUnpivot()) {
            ME me = datasetOptions.getMe();
            ME arg0 = ME.from(me.args.get(0));
            upstream.add(new DatasetSource(datasetId, Long.parseLong(arg0.args.get(1).toString()), datasetOptions.fetchType().toString(), appId, appId));
        }

        upstream.addAll(fetchDatasetsInFilter(datasetOptions, appId, datasetId));
        return upstream;
    }


}
